#include <IDK.hpp>

#include <SceneNode.hpp>


//-------------------------------------------------------
// Update
//-------------------------------------------------------
bool TSceneNode::Update(SceneNode parent, bool parentChanged)
{
	//
	bool changed = mTransformChanged.In(TransformChanged::NeedUpdate);


	if(changed || parentChanged)
	{
		//calculate local by position, orientation?
		if(mTransformChanged.In(TransformChanged::ChangedOrientation) || mTransformChanged.In(TransformChanged::ChangedScale))
		{
			Math::RotateAndScale(mLocalMatrix, mLocalOrientation, mLocalScale);

			mTransformChanged.Exclude(TransformChanged::ChangedOrientation);
			mTransformChanged.Exclude(TransformChanged::ChangedScale);
		}

		if(mTransformChanged.In(TransformChanged::ChangedPosition))
		{
			Math::Position(mLocalMatrix, mLocalPosition);
			mTransformChanged.Exclude(TransformChanged::ChangedPosition);
		}

		// update matrix
		if(parent == NullPtr)
		{
			mWorldMatrix = mLocalMatrix;
			mWorldPosition = mLocalPosition;
			mWorldOrientation = mLocalOrientation;
			mWorldScale = mLocalScale;
		}
		else
		{
			mWorldMatrix = parent->GetWorldMatrix() * mLocalMatrix;
			mWorldPosition = parent->GetWorldPosition() + mLocalPosition;
			mWorldOrientation = parent->GetWorldOrientation() * mLocalOrientation;

			mWorldScale = parent->GetWorldScale() * mLocalScale;
		}
		// trnasform aabb
		mTransformAABB = mWorldMatrix * mOrigAABB;
	}

	mTransformChanged.Exclude(TransformChanged::NeedUpdate);

	return changed;
}

//-------------------------------------------------------
// GetLocalMatrix
//-------------------------------------------------------
const Math::Matrix44& TSceneNode::GetLocalMatrix(void) const
{
	return mLocalMatrix;
}

//-------------------------------------------------------
// GetWorldMatrix
//-------------------------------------------------------
const Math::Matrix44& TSceneNode::GetWorldMatrix(void) const
{
	return mWorldMatrix;
}

//-------------------------------------------------------
// GetOriginalAABBox
//-------------------------------------------------------
const Math::AABB& TSceneNode::GetOriginalAABBox(void) const
{
	return mOrigAABB;
}

//-------------------------------------------------------
// GetTransformAABBox
//-------------------------------------------------------
const Math::AABB& TSceneNode::GetTransformAABBox(void) const
{
	return mTransformAABB;
}
//-------------------------------------------------------
// GetLocalOrientation
//-------------------------------------------------------
const Math::Quaternion& TSceneNode::GetLocalOrientation(void) const
{
	return mLocalOrientation;
}
//-------------------------------------------------------
// GetWorldOrientation
//-------------------------------------------------------
const Math::Quaternion& TSceneNode::GetWorldOrientation(void) const
{
	return mWorldOrientation;
}

//-------------------------------------------------------
// GetLocalScale
//-------------------------------------------------------
const Math::Vector3& TSceneNode::GetLocalScale(void) const
{
	return mLocalScale;
}

//-------------------------------------------------------
// GetWorldScale
//-------------------------------------------------------
const Math::Vector3& TSceneNode::GetWorldScale(void) const
{
	return mWorldScale;
}

//-------------------------------------------------------
// GetLocalPosition
//-------------------------------------------------------
const Math::Vector3& TSceneNode::GetLocalPosition(void) const
{
	return mLocalPosition;
}

//-------------------------------------------------------
// GetWorldPosition
//-------------------------------------------------------
const Math::Vector3& TSceneNode::GetWorldPosition(void) const
{
	return mWorldPosition;
}

//-------------------------------------------------------
// SetOriginalAABBox
//-------------------------------------------------------
void TSceneNode::SetOriginalAABBox(const Math::AABB& aabbox)
{
	mOrigAABB = aabbox;
	mTransformChanged.Include(TransformChanged::NeedUpdate);
}

//-------------------------------------------------------
// SetPosition
//-------------------------------------------------------
void TSceneNode::SetLocalPosition(const Math::Vector3& pos)
{
	//recalulate world
	const Math::Vector3 worldRelative = mWorldPosition - mLocalPosition;

	//set new world position
	mWorldPosition = worldRelative + pos;
	//set new local position
	mLocalPosition = pos;

	mTransformChanged.Include(TransformChanged::NeedUpdate);
	mTransformChanged.Include(TransformChanged::ChangedPosition);
}

//-------------------------------------------------------
// SetLocalScale
//-------------------------------------------------------
void TSceneNode::SetLocalScale(const Math::Vector3& scale)
{
	//recalulate world
	const Math::Vector3 worldRelative = mLocalScale / mWorldScale;

	//set new global position
	mWorldScale = worldRelative * scale;
	//set new local position
	mLocalScale = scale;

	mTransformChanged.Include(TransformChanged::NeedUpdate);
	mTransformChanged.Include(TransformChanged::ChangedScale);
}

//-------------------------------------------------------
// SetTransform
//-------------------------------------------------------
void TSceneNode::SetTransform(const Math::Quaternion& rot, const Math::Vector3& pos)
{
	SetLocalOrientation(rot);
	SetLocalPosition(pos);
}

//-------------------------------------------------------
// SetOrientation
//-------------------------------------------------------
void TSceneNode::SetLocalOrientation(const Math::Quaternion& rot)
{
	//recalulate world
	const Math::Quaternion worldRelative = Math::Inverse(mWorldOrientation) * rot;
	mWorldOrientation = worldRelative * rot;
	mLocalOrientation = rot;
	mTransformChanged.Include(TransformChanged::NeedUpdate);
	mTransformChanged.Include(TransformChanged::ChangedOrientation);
}

//-------------------------------------------------------
// Translate
//-------------------------------------------------------
void TSceneNode::Translate(const Math::Vector3& move)
{
	const Math::Vector3 moveDir = Math::Conjugate(mLocalOrientation) * move;
	SetLocalPosition(mLocalPosition + moveDir);
}

//-------------------------------------------------------
// Rotate
//-------------------------------------------------------
void TSceneNode::Rotate(const Math::Quaternion& rot)
{
	SetLocalOrientation(mLocalOrientation * rot);
}

//
// TSceneNode
//
TSceneNode::TSceneNode(const NodeDesc& desc) 
					   : Base(),
					   mName(desc.name), mVisible(true),
					   mLocalPosition(desc.pos),
					   mLocalOrientation(desc.rot),
					   mWorldPosition(desc.pos), 
					   mWorldOrientation(desc.rot),
					   mOrigAABB(desc.aabbox),
					   mTransformAABB(desc.aabbox),
					   mLocalScale(desc.scale),
					   mWorldScale(desc.scale)
{
	mTransformChanged.Include(TransformChanged::NeedUpdate);
	mTransformChanged.Include(TransformChanged::ChangedOrientation);
	mTransformChanged.Include(TransformChanged::ChangedPosition);
	mTransformChanged.Include(TransformChanged::ChangedScale);
}

const String& TSceneNode::GetName(void) const
{
	return mName;
}

void TSceneNode::SetName(const String &name)
{
	mName = name;
}

void TSceneNode::AddRenderable(ref<IDK::IRenderable> obj)
{
	Add(obj);
}

Array<IDK::Renderable> TSceneNode::GetRenderables()
{
	return Array<IDK::Renderable>(this);
}

bool TSceneNode::Render(IDK::IRenderState& rndState)
{
	if(!mVisible)
		return false;

	{
		const Math::AABB& aabb = GetTransformAABBox();

		if(!aabb.IsInfinity())
		{
			float distance = -Math::Infinity<float>();

			if(rndState.GetOcclusionQuery()->AABBInSpace(aabb, distance) == false)
			{
				mClipped = true;
				//return false;
				return true; //TODO: need parent aabbox transform include all children then this return false, dont Render this, clipped:)
			}
		}

		/*
		//
		//WARNINIG DEBUG INFO
		//
		if(!rndState->GetIgnoredOpperation(IDK::IRenderState::IgnoredOpperation::UsingMaterials))
		{
		//apply transformation
		Math::Matrix44 identity;
		rndState->SetWorldMatrix(identity);
		//Render Debug AABB
		rndState->DrawAABBox(transform->GetTransformAABBox());
		rndState->DrawDebugRay();
		}
		*/

		//apply transformation
		rndState.SetMatrix(IDK::IRenderState::MatrixType::World, GetWorldMatrix());
	}

	//bool retVal = false;
	//bool Render = false;

	for(Iterator it = Begin(), end = End(); it!=end; ++it)
	{
		//Render
		//retVal &= 
		it->Render(rndState);
	}

	mClipped = false;

	return true;
}

void TSceneNode::Rendered(IDK::IRenderState& rndState)
{
	if(!mClipped)
	{
		for(Iterator it = Begin(), end = End(); it!=end; ++it)
		{
			it->Rendered(rndState);
		}
	}
}

bool TSceneNode::IsVisible() const 
{
	return mVisible;
}

void TSceneNode::SetVisible(bool visible)
{
	mVisible = visible;
}